// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/dom_storage/session_storage_database.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <map>
#include <string>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/common/dom_storage/dom_storage_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/iterator.h"
#include "third_party/leveldatabase/src/include/leveldb/options.h"
#include "url/gurl.h"

namespace content {

class SessionStorageDatabaseTest : public testing::Test {
public:
    SessionStorageDatabaseTest();
    ~SessionStorageDatabaseTest() override;
    void SetUp() override;

protected:
    typedef std::map<std::string, std::string> DataMap;

    // Helpers.
    static bool IsNamespaceKey(const std::string& key,
        std::string* namespace_id);
    static bool IsNamespaceOriginKey(const std::string& key,
        std::string* namespace_id);
    static bool IsMapRefCountKey(const std::string& key, int64_t* map_id);
    static bool IsMapValueKey(const std::string& key, int64_t* map_id);
    void ResetDatabase();
    void ReadData(DataMap* data) const;
    void CheckDatabaseConsistency() const;
    void CheckEmptyDatabase() const;
    void DumpData() const;
    void CheckAreaData(const std::string& namespace_id,
        const GURL& origin,
        const DOMStorageValuesMap& reference) const;
    void CompareValuesMaps(const DOMStorageValuesMap& map1,
        const DOMStorageValuesMap& map2) const;
    void CheckNamespaceIds(
        const std::set<std::string>& expected_namespace_ids) const;
    void CheckOrigins(
        const std::string& namespace_id,
        const std::set<GURL>& expected_origins) const;
    std::string GetMapForArea(const std::string& namespace_id,
        const GURL& origin) const;
    int64_t GetMapRefCount(const std::string& map_id) const;

    base::ScopedTempDir temp_dir_;
    scoped_refptr<SessionStorageDatabase> db_;

    // Test data.
    const GURL kOrigin1;
    const GURL kOrigin2;
    const std::string kNamespace1;
    const std::string kNamespace2;
    const std::string kNamespaceClone;
    const base::string16 kKey1;
    const base::string16 kKey2;
    const base::string16 kKey3;
    const base::NullableString16 kValue1;
    const base::NullableString16 kValue2;
    const base::NullableString16 kValue3;
    const base::NullableString16 kValue4;
    const base::NullableString16 kValueNull;

    DISALLOW_COPY_AND_ASSIGN(SessionStorageDatabaseTest);
};

SessionStorageDatabaseTest::SessionStorageDatabaseTest()
    : kOrigin1("http://www.origin1.com")
    , kOrigin2("http://www.origin2.com")
    , kNamespace1("namespace1")
    , kNamespace2("namespace2")
    , kNamespaceClone("wascloned")
    , kKey1(base::ASCIIToUTF16("key1"))
    , kKey2(base::ASCIIToUTF16("key2"))
    , kKey3(base::ASCIIToUTF16("key3"))
    , kValue1(base::ASCIIToUTF16("value1"), false)
    , kValue2(base::ASCIIToUTF16("value2"), false)
    , kValue3(base::ASCIIToUTF16("value3"), false)
    , kValue4(base::ASCIIToUTF16("value4"), false)
{
}

SessionStorageDatabaseTest::~SessionStorageDatabaseTest() { }

void SessionStorageDatabaseTest::SetUp()
{
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    ResetDatabase();
}

void SessionStorageDatabaseTest::ResetDatabase()
{
    db_ = new SessionStorageDatabase(temp_dir_.GetPath());
    ASSERT_TRUE(db_->LazyOpen(true));
}

// static
bool SessionStorageDatabaseTest::IsNamespaceKey(const std::string& key,
    std::string* namespace_id)
{
    std::string namespace_prefix = SessionStorageDatabase::NamespacePrefix();
    if (!base::StartsWith(key, namespace_prefix, base::CompareCase::SENSITIVE))
        return false;
    if (key == namespace_prefix)
        return false;

    size_t second_dash = key.find('-', namespace_prefix.length());
    if (second_dash != key.length() - 1)
        return false;

    // Key is of the form "namespace-<namespaceid>-".
    *namespace_id = key.substr(
        namespace_prefix.length(),
        second_dash - namespace_prefix.length());
    return true;
}

// static
bool SessionStorageDatabaseTest::IsNamespaceOriginKey(
    const std::string& key,
    std::string* namespace_id)
{
    std::string namespace_prefix = SessionStorageDatabase::NamespacePrefix();
    if (!base::StartsWith(key, namespace_prefix, base::CompareCase::SENSITIVE))
        return false;
    size_t second_dash = key.find('-', namespace_prefix.length());
    if (second_dash == std::string::npos || second_dash == key.length() - 1)
        return false;

    // Key is of the form "namespace-<namespaceid>-<origin>", and the value
    // is the map id.
    *namespace_id = key.substr(
        namespace_prefix.length(),
        second_dash - namespace_prefix.length());
    return true;
}

// static
bool SessionStorageDatabaseTest::IsMapRefCountKey(const std::string& key,
    int64_t* map_id)
{
    std::string map_prefix = "map-";
    if (!base::StartsWith(key, map_prefix, base::CompareCase::SENSITIVE))
        return false;
    size_t second_dash = key.find('-', map_prefix.length());
    if (second_dash != key.length() - 1)
        return false;
    // Key is of the form "map-<mapid>-" and the value is the ref count.
    std::string map_id_str = key.substr(map_prefix.length(),
        second_dash - map_prefix.length());
    bool conversion_ok = base::StringToInt64(map_id_str, map_id);
    EXPECT_TRUE(conversion_ok);
    return true;
}

// static
bool SessionStorageDatabaseTest::IsMapValueKey(const std::string& key,
    int64_t* map_id)
{
    std::string map_prefix = "map-";
    if (!base::StartsWith(key, map_prefix, base::CompareCase::SENSITIVE))
        return false;
    size_t second_dash = key.find('-', map_prefix.length());
    if (second_dash == std::string::npos || second_dash == key.length() - 1)
        return false;
    // Key is of the form "map-<mapid>-key".
    std::string map_id_str = key.substr(map_prefix.length(),
        second_dash - map_prefix.length());
    bool conversion_ok = base::StringToInt64(map_id_str, map_id);
    EXPECT_TRUE(conversion_ok);
    return true;
}

void SessionStorageDatabaseTest::ReadData(DataMap* data) const
{
    leveldb::DB* leveldb = db_->db_.get();
    std::unique_ptr<leveldb::Iterator> it(
        leveldb->NewIterator(leveldb::ReadOptions()));
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
        (*data)[it->key().ToString()] = it->value().ToString();
    }
}

void SessionStorageDatabaseTest::CheckDatabaseConsistency() const
{
    DataMap data;
    ReadData(&data);
    // Empty db is ok.
    if (data.empty())
        return;

    // For detecting rubbish keys.
    size_t valid_keys = 0;

    std::string next_map_id_key = SessionStorageDatabase::NextMapIdKey();
    // Check the namespace start key.
    if (data.find(SessionStorageDatabase::NamespacePrefix()) == data.end()) {
        // If there is no namespace start key, the database may contain only counter
        // keys.
        for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
            ASSERT_TRUE(it->first == next_map_id_key);
        }
        return;
    }
    ++valid_keys;

    // Iterate the "namespace-" keys.
    std::set<std::string> found_namespace_ids;
    std::set<std::string> namespaces_with_areas;
    std::map<int64_t, int64_t> expected_map_refcounts;
    int64_t max_map_id = -1;

    for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
        std::string namespace_id;
        if (IsNamespaceKey(it->first, &namespace_id)) {
            found_namespace_ids.insert(namespace_id);
            ++valid_keys;
        } else if (IsNamespaceOriginKey(
                       it->first, &namespace_id)) {
            // Check that the corresponding "namespace-<namespaceid>-" key exists. It
            // has been read by now, since the keys are stored in order.
            ASSERT_TRUE(found_namespace_ids.find(namespace_id) != found_namespace_ids.end());
            namespaces_with_areas.insert(namespace_id);
            int64_t map_id;
            bool conversion_ok = base::StringToInt64(it->second, &map_id);
            ASSERT_TRUE(conversion_ok);
            ASSERT_GE(map_id, 0);
            ++expected_map_refcounts[map_id];
            max_map_id = std::max(map_id, max_map_id);
            ++valid_keys;
        }
    }
    // Check that there are no leftover "namespace-namespaceid-" keys without
    // associated areas.
    ASSERT_EQ(found_namespace_ids.size(), namespaces_with_areas.size());

    if (max_map_id != -1) {
        // The database contains maps.
        ASSERT_TRUE(data.find(next_map_id_key) != data.end());
        int64_t next_map_id;
        bool conversion_ok = base::StringToInt64(data[next_map_id_key], &next_map_id);
        ASSERT_TRUE(conversion_ok);
        ASSERT_GT(next_map_id, max_map_id);
    }

    // Iterate the "map-" keys.
    std::set<int64_t> found_map_ids;
    for (DataMap::const_iterator it = data.begin(); it != data.end(); ++it) {
        int64_t map_id;
        if (IsMapRefCountKey(it->first, &map_id)) {
            int64_t ref_count;
            bool conversion_ok = base::StringToInt64(it->second, &ref_count);
            ASSERT_TRUE(conversion_ok);
            // Check that the map is not stale.
            ASSERT_GT(ref_count, 0);
            ASSERT_TRUE(expected_map_refcounts.find(map_id) != expected_map_refcounts.end());
            ASSERT_EQ(expected_map_refcounts[map_id], ref_count);
            // Mark the map as existing.
            expected_map_refcounts.erase(map_id);
            found_map_ids.insert(map_id);
            ++valid_keys;
        } else if (IsMapValueKey(it->first, &map_id)) {
            ASSERT_TRUE(found_map_ids.find(map_id) != found_map_ids.end());
            ++valid_keys;
        }
    }
    // Check that all maps referred to exist.
    ASSERT_TRUE(expected_map_refcounts.empty());

    if (data.find(next_map_id_key) != data.end())
        ++valid_keys;

    ASSERT_EQ(data.size(), valid_keys);
}

void SessionStorageDatabaseTest::CheckEmptyDatabase() const
{
    DataMap data;
    ReadData(&data);
    size_t valid_keys = 0;
    if (data.find(SessionStorageDatabase::NamespacePrefix()) != data.end())
        ++valid_keys;
    if (data.find(SessionStorageDatabase::NextMapIdKey()) != data.end())
        ++valid_keys;
    EXPECT_EQ(valid_keys, data.size());
}

void SessionStorageDatabaseTest::DumpData() const
{
    LOG(WARNING) << "---- Session storage contents";
    std::unique_ptr<leveldb::Iterator> it(
        db_->db_->NewIterator(leveldb::ReadOptions()));
    for (it->SeekToFirst(); it->Valid(); it->Next()) {
        int64_t dummy_map_id;
        if (IsMapValueKey(it->key().ToString(), &dummy_map_id)) {
            // Convert the value back to base::string16.
            base::string16 value;
            size_t len = it->value().size() / sizeof(base::char16);
            value.resize(len);
            value.assign(
                reinterpret_cast<const base::char16*>(it->value().data()), len);
            LOG(WARNING) << it->key().ToString() << ": " << value;
        } else {
            LOG(WARNING) << it->key().ToString() << ": " << it->value().ToString();
        }
    }
    LOG(WARNING) << "----";
}

void SessionStorageDatabaseTest::CheckAreaData(
    const std::string& namespace_id, const GURL& origin,
    const DOMStorageValuesMap& reference) const
{
    DOMStorageValuesMap values;
    db_->ReadAreaValues(namespace_id, origin, &values);
    CompareValuesMaps(values, reference);
}

void SessionStorageDatabaseTest::CompareValuesMaps(
    const DOMStorageValuesMap& map1,
    const DOMStorageValuesMap& map2) const
{
    ASSERT_EQ(map2.size(), map1.size());
    for (DOMStorageValuesMap::const_iterator it = map1.begin();
         it != map1.end(); ++it) {
        base::string16 key = it->first;
        ASSERT_TRUE(map2.find(key) != map2.end());
        base::NullableString16 val1 = it->second;
        base::NullableString16 val2 = map2.find(key)->second;
        EXPECT_EQ(val2.is_null(), val1.is_null());
        EXPECT_EQ(val2.string(), val1.string());
    }
}

void SessionStorageDatabaseTest::CheckNamespaceIds(
    const std::set<std::string>& expected_namespace_ids) const
{
    std::map<std::string, std::vector<GURL>> namespaces_and_origins;
    EXPECT_TRUE(db_->ReadNamespacesAndOrigins(&namespaces_and_origins));
    EXPECT_EQ(expected_namespace_ids.size(), namespaces_and_origins.size());
    for (std::map<std::string, std::vector<GURL>>::const_iterator it = namespaces_and_origins.begin();
         it != namespaces_and_origins.end(); ++it) {
        EXPECT_TRUE(expected_namespace_ids.find(it->first) != expected_namespace_ids.end());
    }
}

void SessionStorageDatabaseTest::CheckOrigins(
    const std::string& namespace_id,
    const std::set<GURL>& expected_origins) const
{
    std::map<std::string, std::vector<GURL>> namespaces_and_origins;
    EXPECT_TRUE(db_->ReadNamespacesAndOrigins(&namespaces_and_origins));
    const std::vector<GURL>& origins = namespaces_and_origins[namespace_id];
    EXPECT_EQ(expected_origins.size(), origins.size());
    for (std::vector<GURL>::const_iterator it = origins.begin();
         it != origins.end(); ++it) {
        EXPECT_TRUE(expected_origins.find(*it) != expected_origins.end());
    }
}

std::string SessionStorageDatabaseTest::GetMapForArea(
    const std::string& namespace_id, const GURL& origin) const
{
    bool exists;
    std::string map_id;
    EXPECT_TRUE(db_->GetMapForArea(namespace_id, origin.spec(),
        leveldb::ReadOptions(), &exists, &map_id));
    EXPECT_TRUE(exists);
    return map_id;
}

int64_t SessionStorageDatabaseTest::GetMapRefCount(
    const std::string& map_id) const
{
    int64_t ref_count;
    EXPECT_TRUE(db_->GetMapRefCount(map_id, &ref_count));
    return ref_count;
}

TEST_F(SessionStorageDatabaseTest, EmptyDatabaseSanityCheck)
{
    // An empty database should be valid.
    CheckDatabaseConsistency();
}

TEST_F(SessionStorageDatabaseTest, WriteDataForOneOrigin)
{
    // Keep track on what the values should look like.
    DOMStorageValuesMap reference;
    // Write data.
    {
        DOMStorageValuesMap changes;
        changes[kKey1] = kValue1;
        changes[kKey2] = kValue2;
        changes[kKey3] = kValue3;
        reference[kKey1] = kValue1;
        reference[kKey2] = kValue2;
        reference[kKey3] = kValue3;
        EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, changes));
    }
    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, reference);

    // Overwrite and delete values.
    {
        DOMStorageValuesMap changes;
        changes[kKey1] = kValue4;
        changes[kKey3] = kValueNull;
        reference[kKey1] = kValue4;
        reference.erase(kKey3);
        EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, changes));
    }
    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, reference);

    // Clear data before writing.
    {
        DOMStorageValuesMap changes;
        changes[kKey2] = kValue2;
        reference.erase(kKey1);
        reference[kKey2] = kValue2;
        EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, true, changes));
    }
    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, reference);
}

TEST_F(SessionStorageDatabaseTest, WriteDataForTwoOrigins)
{
    // Write data.
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));

    DOMStorageValuesMap data2;
    data2[kKey1] = kValue4;
    data2[kKey2] = kValue1;
    data2[kKey3] = kValue2;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));

    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, data1);
    CheckAreaData(kNamespace1, kOrigin2, data2);
}

TEST_F(SessionStorageDatabaseTest, WriteDataForTwoNamespaces)
{
    // Write data.
    DOMStorageValuesMap data11;
    data11[kKey1] = kValue1;
    data11[kKey2] = kValue2;
    data11[kKey3] = kValue3;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data11));
    DOMStorageValuesMap data12;
    data12[kKey2] = kValue4;
    data12[kKey3] = kValue3;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data12));
    DOMStorageValuesMap data21;
    data21[kKey1] = kValue2;
    data21[kKey2] = kValue4;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin1, false, data21));
    DOMStorageValuesMap data22;
    data22[kKey2] = kValue1;
    data22[kKey3] = kValue2;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin2, false, data22));
    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, data11);
    CheckAreaData(kNamespace1, kOrigin2, data12);
    CheckAreaData(kNamespace2, kOrigin1, data21);
    CheckAreaData(kNamespace2, kOrigin2, data22);
}

TEST_F(SessionStorageDatabaseTest, ShallowCopy)
{
    // Write data for a namespace, for 2 origins.
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    DOMStorageValuesMap data2;
    data2[kKey1] = kValue2;
    data2[kKey3] = kValue1;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
    // Make a shallow copy.
    EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
    // Now both namespaces should have the same data.
    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, data1);
    CheckAreaData(kNamespace1, kOrigin2, data2);
    CheckAreaData(kNamespaceClone, kOrigin1, data1);
    CheckAreaData(kNamespaceClone, kOrigin2, data2);
    // Both the namespaces refer to the same maps.
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
        GetMapForArea(kNamespaceClone, kOrigin1));
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
        GetMapForArea(kNamespaceClone, kOrigin2));
    EXPECT_EQ(2, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin1)));
    EXPECT_EQ(2, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin2)));
}

TEST_F(SessionStorageDatabaseTest, WriteIntoShallowCopy)
{
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));

    // Write data into a shallow copy.
    DOMStorageValuesMap changes;
    DOMStorageValuesMap reference;
    changes[kKey1] = kValueNull;
    changes[kKey2] = kValue4;
    changes[kKey3] = kValue4;
    reference[kKey2] = kValue4;
    reference[kKey3] = kValue4;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespaceClone, kOrigin1, false,
        changes));

    // Values in the original namespace were not changed.
    CheckAreaData(kNamespace1, kOrigin1, data1);
    // But values in the copy were.
    CheckAreaData(kNamespaceClone, kOrigin1, reference);

    // The namespaces no longer refer to the same map.
    EXPECT_NE(GetMapForArea(kNamespace1, kOrigin1),
        GetMapForArea(kNamespaceClone, kOrigin1));
    EXPECT_EQ(1, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin1)));
    EXPECT_EQ(1, GetMapRefCount(GetMapForArea(kNamespaceClone, kOrigin1)));
}

TEST_F(SessionStorageDatabaseTest, ManyShallowCopies)
{
    // Write data for a namespace, for 2 origins.
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    DOMStorageValuesMap data2;
    data2[kKey1] = kValue2;
    data2[kKey3] = kValue1;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));

    // Make a two shallow copies.
    EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
    std::string another_clone("another_cloned");
    EXPECT_TRUE(db_->CloneNamespace(kNamespace1, another_clone));

    // Make a shallow copy of a shallow copy.
    std::string clone_of_clone("clone_of_clone");
    EXPECT_TRUE(db_->CloneNamespace(another_clone, clone_of_clone));

    // Now all namespaces should have the same data.
    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, data1);
    CheckAreaData(kNamespaceClone, kOrigin1, data1);
    CheckAreaData(another_clone, kOrigin1, data1);
    CheckAreaData(clone_of_clone, kOrigin1, data1);
    CheckAreaData(kNamespace1, kOrigin2, data2);
    CheckAreaData(kNamespaceClone, kOrigin2, data2);
    CheckAreaData(another_clone, kOrigin2, data2);
    CheckAreaData(clone_of_clone, kOrigin2, data2);

    // All namespaces refer to the same maps.
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
        GetMapForArea(kNamespaceClone, kOrigin1));
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
        GetMapForArea(kNamespaceClone, kOrigin2));
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
        GetMapForArea(another_clone, kOrigin1));
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
        GetMapForArea(another_clone, kOrigin2));
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin1),
        GetMapForArea(clone_of_clone, kOrigin1));
    EXPECT_EQ(GetMapForArea(kNamespace1, kOrigin2),
        GetMapForArea(clone_of_clone, kOrigin2));

    // Check the ref counts.
    EXPECT_EQ(4, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin1)));
    EXPECT_EQ(4, GetMapRefCount(GetMapForArea(kNamespace1, kOrigin2)));
}

TEST_F(SessionStorageDatabaseTest, DisassociateShallowCopy)
{
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));

    // Disassoaciate the shallow copy.
    EXPECT_TRUE(db_->DeleteArea(kNamespaceClone, kOrigin1));
    CheckDatabaseConsistency();

    // Now new data can be written to that map.
    DOMStorageValuesMap reference;
    DOMStorageValuesMap changes;
    changes[kKey1] = kValueNull;
    changes[kKey2] = kValue4;
    changes[kKey3] = kValue4;
    reference[kKey2] = kValue4;
    reference[kKey3] = kValue4;
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespaceClone, kOrigin1, false,
        changes));

    // Values in the original map were not changed.
    CheckAreaData(kNamespace1, kOrigin1, data1);

    // But values in the disassociated map were.
    CheckAreaData(kNamespaceClone, kOrigin1, reference);
}

TEST_F(SessionStorageDatabaseTest, DeleteNamespace)
{
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    DOMStorageValuesMap data2;
    data2[kKey2] = kValue4;
    data2[kKey3] = kValue3;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));
    EXPECT_TRUE(db_->DeleteNamespace(kNamespace1));
    CheckDatabaseConsistency();
    CheckEmptyDatabase();
}

TEST_F(SessionStorageDatabaseTest, DeleteNamespaceWithShallowCopy)
{
    // Write data for a namespace, for 2 origins.
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    DOMStorageValuesMap data2;
    data2[kKey1] = kValue2;
    data2[kKey3] = kValue1;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));

    // Make a shallow copy and delete the original namespace.
    EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
    EXPECT_TRUE(db_->DeleteNamespace(kNamespace1));

    // The original namespace has no data.
    CheckDatabaseConsistency();
    CheckAreaData(kNamespace1, kOrigin1, DOMStorageValuesMap());
    CheckAreaData(kNamespace1, kOrigin2, DOMStorageValuesMap());
    // But the copy persists.
    CheckAreaData(kNamespaceClone, kOrigin1, data1);
    CheckAreaData(kNamespaceClone, kOrigin2, data2);
}

TEST_F(SessionStorageDatabaseTest, DeleteArea)
{
    // Write data for a namespace, for 2 origins.
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    DOMStorageValuesMap data2;
    data2[kKey1] = kValue2;
    data2[kKey3] = kValue1;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));

    EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
    CheckDatabaseConsistency();
    // The data for the non-deleted origin persists.
    CheckAreaData(kNamespace1, kOrigin1, data1);
    // The data for the deleted origin is gone.
    CheckAreaData(kNamespace1, kOrigin2, DOMStorageValuesMap());
}

TEST_F(SessionStorageDatabaseTest, DeleteAreaWithShallowCopy)
{
    // Write data for a namespace, for 2 origins.
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    DOMStorageValuesMap data2;
    data2[kKey1] = kValue2;
    data2[kKey3] = kValue1;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));

    // Make a shallow copy and delete an origin from the original namespace.
    EXPECT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
    EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin1));
    CheckDatabaseConsistency();

    // The original namespace has data for only the non-deleted origin.
    CheckAreaData(kNamespace1, kOrigin1, DOMStorageValuesMap());
    CheckAreaData(kNamespace1, kOrigin2, data2);
    // But the copy persists.
    CheckAreaData(kNamespaceClone, kOrigin1, data1);
    CheckAreaData(kNamespaceClone, kOrigin2, data2);
}

TEST_F(SessionStorageDatabaseTest, WriteRawBytes)
{
    // Write data which is not valid utf8 and contains null bytes.
    unsigned char raw_data[10] = { 255, 0, 0, 0, 1, 2, 3, 4, 5, 0 };
    DOMStorageValuesMap changes;
    base::string16 string_with_raw_data;
    string_with_raw_data.assign(reinterpret_cast<base::char16*>(raw_data), 5);
    changes[kKey1] = base::NullableString16(string_with_raw_data, false);
    EXPECT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, changes));
    CheckDatabaseConsistency();
    DOMStorageValuesMap values;
    db_->ReadAreaValues(kNamespace1, kOrigin1, &values);
    const unsigned char* data = reinterpret_cast<const unsigned char*>(values[kKey1].string().data());
    for (int i = 0; i < 10; ++i)
        EXPECT_EQ(raw_data[i], data[i]);
}

TEST_F(SessionStorageDatabaseTest, DeleteNamespaceConfusion)
{
    // Regression test for a bug where a namespace with id 10 prevented deleting
    // the namespace with id 1.

    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    ASSERT_TRUE(db_->CommitAreaChanges("foobar", kOrigin1, false, data1));
    ASSERT_TRUE(db_->CommitAreaChanges("foobarbaz", kOrigin1, false, data1));

    // Delete the namespace with ID 1.
    EXPECT_TRUE(db_->DeleteNamespace("foobar"));
}

TEST_F(SessionStorageDatabaseTest, ReadNamespaceIds)
{
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;
    std::set<std::string> expected_namespace_ids;

    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    expected_namespace_ids.insert(kNamespace1);
    CheckNamespaceIds(expected_namespace_ids);

    ASSERT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
    expected_namespace_ids.insert(kNamespaceClone);
    CheckNamespaceIds(expected_namespace_ids);

    ASSERT_TRUE(db_->DeleteNamespace(kNamespace1));
    expected_namespace_ids.erase(kNamespace1);
    CheckNamespaceIds(expected_namespace_ids);

    CheckDatabaseConsistency();
}

TEST_F(SessionStorageDatabaseTest, ReadNamespaceIdsInEmptyDatabase)
{
    std::set<std::string> expected_namespace_ids;
    CheckNamespaceIds(expected_namespace_ids);
}

TEST_F(SessionStorageDatabaseTest, ReadOriginsInNamespace)
{
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    data1[kKey2] = kValue2;
    data1[kKey3] = kValue3;

    std::set<GURL> expected_origins1;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data1));
    expected_origins1.insert(kOrigin1);
    expected_origins1.insert(kOrigin2);
    CheckOrigins(kNamespace1, expected_origins1);

    std::set<GURL> expected_origins2;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace2, kOrigin2, false, data1));
    expected_origins2.insert(kOrigin2);
    CheckOrigins(kNamespace2, expected_origins2);

    ASSERT_TRUE(db_->CloneNamespace(kNamespace1, kNamespaceClone));
    CheckOrigins(kNamespaceClone, expected_origins1);

    ASSERT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
    expected_origins1.erase(kOrigin2);
    CheckOrigins(kNamespace1, expected_origins1);

    CheckDatabaseConsistency();
}

TEST_F(SessionStorageDatabaseTest, DeleteAllOrigins)
{
    // Write data for a namespace, for 2 origins.
    DOMStorageValuesMap data1;
    data1[kKey1] = kValue1;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin1, false, data1));
    DOMStorageValuesMap data2;
    data2[kKey1] = kValue2;
    ASSERT_TRUE(db_->CommitAreaChanges(kNamespace1, kOrigin2, false, data2));

    EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin1));
    EXPECT_TRUE(db_->DeleteArea(kNamespace1, kOrigin2));
    // Check that also the namespace start key was deleted.
    CheckDatabaseConsistency();
}

} // namespace content
